/**
 * Modules is the class used to manage Module objects. It can create, edit, or delete a Module
 * and add or delete Version object to/from this Module.
 * 
 * @author Eric LEON
 * @version 1.0.0
 */
package controllers;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.persistence.PersistenceException;

import models.Module;
import models.Repository;
import models.Version;
import play.Logger;
import play.i18n.Messages;
import play.mvc.With;

@With(Secure.class)
public class Modules extends RepoController {
	
	/**
	 * Creates a new module object and edit it.
	 */
	public static void create(String m_idx, String r_idx, String t_idx) {
		// Create a new module object
		Module mod = new Module();
		// Generate a generic name
		mod.name = String.format("module%d", Module.count() + 1);
		// Save index page layout
		saveLayout(m_idx, r_idx, t_idx);
		// Show module page
		render("@showModule", mod);
	}

	/**
	 * Show the module identified by its id.
	 * @param id	The Module id.
	 */
	public static void show(String m_idx, String r_idx, String t_idx, Long id) {
		// Retrieve the module object
		Module mod = Module.findById(id);
		// Save index page layout
		saveLayout(m_idx, r_idx, t_idx);
		// Show module page
		render("@showModule", mod);
	}

	/**
	 * Deletes the module identified by its id.
	 * @param id	The Module id.
	 */
	public static void delete(String m_idx, String r_idx, String t_idx, Long id) {
		// Retrieve the module object
		Module mod = Module.findById(id);
		// Remove module from its repositories
		Repository.removeModule(mod);
		// Delete it
		mod.delete();
		// Save index page layout
		saveLayout(m_idx, r_idx, t_idx);
		// Display index page
		Application.index();
	}

	/**
	 * Creates a new module, or edits an existing one. The version parameters must only be present
	 * when a new module is created: they contains the initial version of this module.
	 * When creating a new module, the id field is null because the object has not been persisted yet.
	 * When editing an existing module, this id represents the original object JPA identifier.
	 * 
	 * @param mod		the module new content
	 * @param id		the original module object id. null when object is created.
	 * @param number	the version string when object is created, else null
	 * @param matches	the matching Play! framework version when object is created, else null
	 * @param archive	the archive file object when object is created, else null
	 */
	public static void save(Module mod, Long id, String number, String matches, File archive) {
		
		// Keep index page layout
		keepLayout();

		validation.required("mod.name", mod.name);
		validation.required("mod.license", mod.license);

		Version ver = null;
		Module old = null;
		
		if (!validation.hasErrors()) {
			if (id == null) {
				// Module not yet created
				old = null;
				// Module name must be unique: find it into the database
				if (Module.count("name", mod.name) > 0) {
					// Module name already created, generate a validation error 
					validation.addError("mod.name", Messages.get("module.uniquename"));
				} else {
					// First version parameters must be validated
					validation.required(number);
					validation.required(matches);
					validation.required("archive", archive);
					// No error, read the archive file
					if (!validation.hasErrors()) {
						// Create the first version object
						ver = new Version(number, true, matches, archive.getName(), null);
						// Read the archive content
						if (!readArchive(ver, archive)) {
							validation.addError("archive", Messages.get("module.archive"));
						}
					}
				}
			} else {
				// Retrieve original module from database
				old = Module.findById(id);
				// Test for module rename
				if (!old.name.equals(mod.name)) {
					// It must be unique, see if it already exists into the database
					if (Module.count("name", mod.name) > 0) {
						// Module name already created, generate a validation error 
						validation.addError("mod.name", Messages.get("module.uniquename"));
					}
				}
			}
		}
		
		if (!validation.hasErrors()) {
	    	// No validation error, save the created module object
	    	try {
	    		if (old != null) {
		    		// Save it into database
					old.edit("mod", params.all());
					old.save();
	    		} else {
		    		// Save it into database
					mod.addVersion(ver);
	    		}
		    	// Display main page
				Application.index();
		        return;
	    	} catch (PersistenceException pe) {
				flash.error(Messages.get("module.createfailure", pe.getMessage()));
	    	} catch (Exception e) {
				flash.error(Messages.get("module.createfailure", e.getMessage()));
	    	}
	    }
		
        // Display the same page
		if (id != null) {
	        // Restore module object from database to populate versions collection
	        mod = Module.findById(id);
		}

	    render("@showModule", mod, number, matches);
	}

	/**
	 * Adds a new version to a module object. A version consists into a number string,
	 * a Play! framework compatibility version string and a file archive.
	 * 
	 * @param id		the module id
	 * @param number	the module version string
	 * @param matches	the matching Play! framework version
	 * @param archive	the module archive file object
	 */
	public static void addVersion(Long id, String number, String matches, File archive) {
		
		// Keep index page layout
		keepLayout();

		validation.required(number);
		validation.required(matches);
		validation.required(archive);

		Version ver = null;
		Module mod = null;
		
		try { 
			mod = Module.findById(id);
		} catch (Exception e) {
			Logger.error("FindById error %s", e.getMessage());
			validation.addError("id", Messages.get("module.id", e.getMessage()));
		}
		
		if (!validation.hasErrors()) {
			if (!number.equals("head") && mod.getVersion(number) != null) {
				Logger.error("Version %s is not unique for module %s", number, mod);
				validation.addError("number", Messages.get("version.notunique"));
			}
		}
		
		if (!validation.hasErrors()) {
			ver = new Version(number, false, matches, archive.getName(), null);
			if (!readArchive(ver, archive)) {
				validation.addError("archive", Messages.get("module.archive"));
			}
		}
		
		if (!validation.hasErrors()) {
	    	// No validation error, save the created module object
	    	try {
				// Create a new version for this module
				mod.addVersion(ver);
		    	// Refresh the same page
	    	} catch (PersistenceException pe) {
				flash.error(Messages.get("version.createfailure", pe.getMessage()));
	    	} catch (Exception e) {
				flash.error(Messages.get("module.createfailure", e.getMessage()));
	    	}
	    }

        // Display the same page
        render("@showModule", mod);
	}

	/**
	 * Deletes a version from a module object. The version is removed from the module versions collection.
	 * 
	 * @param id		the module id
	 * @param number	the module version string
	 */
	public static void deleteVersion(Long id, String number) {
		
		// Keep index page layout
		keepLayout();

		Module mod = null;
		
		try { 
			mod = Module.findById(id);
			mod.deleteVersion(number);
	    	// Refresh the same page
		} catch (Exception e) {
			flash.error("versions.deletefailure", e.getMessage());
		}
		
        // Display the same page
        render("@showModule", mod);
	}

	/**
	 * Marks the given version as default one for this module. A module can only have one version marked as default.
	 * All other versions will have their default flag unset.
	 * 
	 * @param id		the module id
	 * @param number	the version string that will be the default
	 */
	public static void setDefault(Long id, String number) {
		
		// Keep index page layout
		keepLayout();

		Module mod = null;
		
		try { 
			mod = Module.findById(id);
			mod.defaultVersion(number);
	    	// Refresh the same page
		} catch (Exception e) {
			flash.error("versions.defaultfailure", e.getMessage());
		}

        // Display the same page
        render("@showModule", mod);
	}

	/**
	 * Sends the given module version code to the remote. The code will be rendered as a binary content.
	 * 
	 * @param id		the module id
	 * @param number	the version number
	 */
	public static void download(Long id, String number) {
		
		// Keep index page layout
		keepLayout();
		
		Version ver = null;
		Module mod = null;
		
		try { 
			mod = Module.findById(id);
			ver = mod.getVersion(number);
			if (ver != null) {
				ByteArrayInputStream is = new ByteArrayInputStream(ver.code);
				renderBinary(is, ver.filename);
				// Do not refresh page
				return;
			} else {
				flash.error("versions.loadnotfound", number);
			}
		} catch (Exception e) {
			flash.error("versions.loadfailure", e.getMessage());
		}
		
        // Display the same page
        render("@showModule", mod);
	}
	
	public static boolean readArchive(Version ver, File archive) {
		
		FileInputStream fis = null;
		boolean ret = false;
		
		try {
			// Read module file content
        	fis = new FileInputStream(archive);
        	ver.code = new byte[fis.available()];
	        fis.read(ver.code);
    	} catch (Throwable e) {
    		Logger.error("Error reading %s: %s", archive.getName(), e.getMessage());
    		e.printStackTrace();
    	} finally {
    		if (fis != null) {
    			try {
    				fis.close();
    				ret = true;
    			} catch (IOException e) {
    				Logger.error("Error closing %s: %s", archive.getName(), e.getMessage());
    			}
				fis = null;
    		}
    	}
    	
    	return ret;
	}
}
